﻿using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace PmSoft.Cache.Abstractions;

/// <summary>
/// 默认缓存服务，仅用于实体缓存服务，不支持其他数据缓存
/// </summary>
public class DefaultCacheService : ICacheService
{
	private readonly ICache _cache;
	private readonly ICache _localCache;
	private readonly CacheServiceOptions _options;
	private readonly ILogger _logger;
	private readonly Dictionary<CachingExpirationType, TimeSpan> _cachingExpirationDictionary;

	/// <summary>
	/// 构造函数，初始化实体缓存服务
	/// </summary>
	/// <param name="logger">日志记录器</param>
	/// <param name="optionsAccessor">缓存服务选项访问器</param>
	/// <param name="localCache">本地内存缓存</param>
	/// <param name="distributedCache">分布式缓存</param>
	public DefaultCacheService(
		ILogger<DefaultCacheService> logger,
		IOptions<CacheServiceOptions> optionsAccessor,
		IMemoryCache localCache,
		IDistributedCache? distributedCache = null
	)
	{
		_options = optionsAccessor?.Value ?? throw new ArgumentNullException(nameof(optionsAccessor));
		_logger = logger ?? throw new ArgumentNullException(nameof(logger));

		_localCache = new RuntimeMemoryCache(localCache ?? throw new ArgumentNullException(nameof(localCache)));
		_cache = _options.EnableDistributedCache && distributedCache != null
			? new DistributedMemoryCache(distributedCache ?? throw new ArgumentNullException(nameof(distributedCache)))
			: _localCache;

		EnableDistributedCache = _options.EnableDistributedCache;

		_cachingExpirationDictionary = new Dictionary<CachingExpirationType, TimeSpan>
		{
			[CachingExpirationType.Invariable] = TimeSpan.FromSeconds(86400f * _options.CacheExpirationFactor),
			[CachingExpirationType.Stable] = TimeSpan.FromSeconds(28800f * _options.CacheExpirationFactor),
			[CachingExpirationType.RelativelyStable] = TimeSpan.FromSeconds(7200f * _options.CacheExpirationFactor),
			[CachingExpirationType.UsualSingleObject] = TimeSpan.FromSeconds(600f * _options.CacheExpirationFactor),
			[CachingExpirationType.UsualObjectCollection] = TimeSpan.FromSeconds(300f * _options.CacheExpirationFactor),
			[CachingExpirationType.SingleObject] = TimeSpan.FromSeconds(180f * _options.CacheExpirationFactor),
			[CachingExpirationType.ObjectCollection] = TimeSpan.FromSeconds(180f * _options.CacheExpirationFactor)
		};

		_logger.LogInformation("实体缓存服务初始化，分布式缓存启用: {EnableDistributedCache}, 缓存过期因子: {CacheExpirationFactor}",
			_options.EnableDistributedCache, _options.CacheExpirationFactor);
	}

	/// <summary>
	/// 是否启用分布式缓存
	/// </summary>
	public bool EnableDistributedCache { get; }

	/// <summary>
	/// 从缓存获取实体（同步）
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <returns>缓存的实体，若不存在则返回 null</returns>
	public T? Get<T>(string cacheKey)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		T? value = EnableDistributedCache ? _localCache.Get<T>(cacheKey) : default;
		if (value != null) return value;

		value = _cache.Get<T>(cacheKey);
		if (EnableDistributedCache && value != null)
		{
			_localCache.Set(cacheKey, value, _cachingExpirationDictionary[CachingExpirationType.SingleObject]);
		}
		return value;
	}

	/// <summary>
	/// 从缓存获取实体（异步）
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <returns>缓存的实体，若不存在则返回 null</returns>
	public async Task<T?> GetAsync<T>(string cacheKey)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		T? value = EnableDistributedCache ? await _localCache.GetAsync<T>(cacheKey) : default;
		if (value != null) return value;

		value = await _cache.GetAsync<T>(cacheKey);
		if (EnableDistributedCache && value != null)
		{
			await _localCache.SetAsync(cacheKey, value, _cachingExpirationDictionary[CachingExpirationType.SingleObject]);
		}
		return value;
	}

	/// <summary>
	/// 从一级缓存获取实体（同步），分布式模式下直接访问分布式缓存
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <returns>缓存的实体，若不存在则返回 null</returns>
	public T? GetFromFirstLevel<T>(string cacheKey) where T : class
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		return _cache.Get<T>(cacheKey);
	}

	/// <summary>
	/// 从一级缓存获取实体（异步），分布式模式下直接访问分布式缓存
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <returns>缓存的实体，若不存在则返回 null</returns>
	public async Task<T?> GetFromFirstLevelAsync<T>(string cacheKey) where T : class
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		return await _cache.GetAsync<T>(cacheKey);
	}

	/// <summary>
	/// 移除实体缓存（同步）
	/// </summary>
	/// <param name="cacheKey">缓存键</param>
	public void Remove(string cacheKey)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		_cache.Remove(cacheKey);
		if (EnableDistributedCache) _localCache.Remove(cacheKey);
	}

	/// <summary>
	/// 移除实体缓存（异步）
	/// </summary>
	/// <param name="cacheKey">缓存键</param>
	public async Task RemoveAsync(string cacheKey)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		await _cache.RemoveAsync(cacheKey);
		if (EnableDistributedCache) await _localCache.RemoveAsync(cacheKey);
	}

	/// <summary>
	/// 设置实体缓存（同步，指定时间）
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <param name="value">实体值</param>
	/// <param name="timeSpan">过期时间</param>
	public void Set<T>(string cacheKey, T value, TimeSpan timeSpan)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		_cache.Set(cacheKey, value, timeSpan);
		if (EnableDistributedCache) _localCache.Set(cacheKey, value, timeSpan);
	}

	/// <summary>
	/// 设置实体缓存（同步，指定过期类型）
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <param name="value">实体值</param>
	/// <param name="cachingExpirationType">缓存过期类型</param>
	public void Set<T>(string cacheKey, T value, CachingExpirationType cachingExpirationType)
	{
		Set(cacheKey, value, _cachingExpirationDictionary[cachingExpirationType]);
	}

	/// <summary>
	/// 设置实体缓存（异步，指定时间）
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <param name="value">实体值</param>
	/// <param name="timeSpan">过期时间</param>
	public async Task SetAsync<T>(string cacheKey, T value, TimeSpan timeSpan)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		await _cache.SetAsync(cacheKey, value, timeSpan);
		if (EnableDistributedCache) await _localCache.SetAsync(cacheKey, value, timeSpan);
	}

	/// <summary>
	/// 设置实体缓存（异步，指定过期类型）
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <param name="value">实体值</param>
	/// <param name="cachingExpirationType">缓存过期类型</param>
	public async Task SetAsync<T>(string cacheKey, T value, CachingExpirationType cachingExpirationType)
	{
		await SetAsync(cacheKey, value, _cachingExpirationDictionary[cachingExpirationType]);
	}

	/// <summary>
	/// 尝试获取实体缓存
	/// </summary>
	/// <typeparam name="T">实体类型</typeparam>
	/// <param name="cacheKey">缓存键</param>
	/// <param name="value">输出缓存值</param>
	/// <returns>是否成功获取</returns>
	public bool TryGetValue<T>(string cacheKey, out T? value)
	{
		if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentNullException(nameof(cacheKey));
		value = Get<T>(cacheKey);
		return value != null;
	}
}